/*
 * Aritmetico.cpp
 *
 *  Created on: Apr 2, 2012
 *      Author: damian
 */

#include "Aritmetico.h"

namespace
{
const int CANTIDAD_BITS = sizeof(uint32_t) * 8;
}
Aritmetico::Aritmetico()
{
    inicio = CERO_BITS;
    fin = UNOS_BIT;
    underflowCounter = 0;
    codigo = 0;
    primeraLlamada = true;
}

Aritmetico::~Aritmetico()
{
}

void Aritmetico::comprimir(SimboloAritmetico simbolo, ostream& salida)
{
    actualizarLimites(simbolo);
    renormalizar(salida);
}

void Aritmetico::actualizarLimites(SimboloAritmetico s)
{
    // Rango entre piso y techo
    uint64_t rango = (uint64_t)(fin - inicio) + 1;

    // inicio + [(rango / frTotalDelContexto) * finDelRangoRelativoDelSimboloEnElContexto]
    fin = inicio + (uint32_t) (((uint64_t)(rango * s.fin)) / s.total - 1);

    // inicio + [(rango / frTotalDelContexto) * inicioDelRangoRelativoDelSimboloEnElContexto]
    inicio = inicio + (uint32_t) (((uint64_t)(rango * s.inicio)) / s.total);
}

void Aritmetico::renormalizar(ostream& salida)
{
    while (true)
    {
        // Si el primer bits es igual.
        if ((fin & PRIMER_BIT) == (inicio & PRIMER_BIT))
        {
            // Escribe el bit en archivo junto con los bits acumulados de underflow
            bp.escribir_bit(fin & PRIMER_BIT, salida);
            while (underflowCounter > 0)
            {
                bp.escribir_bit(~fin & PRIMER_BIT, salida);
                underflowCounter--;
            }
        }
        // Si hay underflow
        else if ((inicio & SEGUNDO_BIT) && !(fin & SEGUNDO_BIT))
        {
            // Aumento el contador de undeflow y modifico el segundo bit de los limites.
            underflowCounter++;
            inicio &= TODOS_MENOS_DOS_PRIMEROS_BITS;
            fin |= SEGUNDO_BIT;
        }
        else
        {
            // Si no tengo bit a escribir ni hay undeflow, salgo.
            return;
        }
        // Luego de procesar, desplazo los bits de los limites a la izquierda y agrego un 1 al final de 'fin'.
        inicio <<= 1;
        fin <<= 1;
        fin |= ULTIMO_BIT_UNO;
    }
}

void Aritmetico::flush_comprimir(ostream& salida)
{
    bp.escribir_bit(inicio & SEGUNDO_BIT, salida);
    underflowCounter++;
    while (underflowCounter-- > 0)
        bp.escribir_bit(~inicio & SEGUNDO_BIT, salida);
    bp.flush(salida);
}

/*
 * Descomprimir
 */

uint32_t Aritmetico::descomprimir(SimboloAritmetico sa, istream& entrada)
{
    if (primeraLlamada)
    // Inicializar
    {
        // Leo los primeros 16 bits del archivo.
        for (int i = 0; i < CANTIDAD_BITS; i++)
        {
            codigo <<= 1;
            codigo += bp.leer_bit(entrada);
        }
        primeraLlamada = false;
    }
    // A partir de la frecuencia total y del codigo leido saco la frecuencia relativa al contexto.
    uint32_t frecuenciaLeida = calcularFrecuenciaLeida(sa);
    return frecuenciaLeida;
}

uint32_t Aritmetico::calcularFrecuenciaLeida(SimboloAritmetico sa)
{
    uint32_t frecuenciaSimbolo;
    // Rango entre piso y techo
    uint64_t rango = ((uint64_t)(fin - inicio)) + 1;

    // ('codigo leido' - 'inicio') * ('frecuencia total del contexto'/ rango)
    frecuenciaSimbolo =
            (uint32_t) ((((uint64_t) (codigo - inicio) + 1) * sa.total
                    - 1) / rango);
    return frecuenciaSimbolo;
}

void Aritmetico::quitarCaracterDescomprimido(SimboloAritmetico sa,
        istream& entrada)
{
    // Misma logica que la normalizacion del compresor para quitar los bits 'consumidos' al decodificar el caracter 'sa'.
    uint64_t rango = (uint64_t)(fin - inicio) + 1;

    fin = inicio + (uint32_t) (((uint64_t)(rango * sa.fin)) / sa.total - 1);
    inicio = inicio + (uint32_t) (((uint64_t)(rango * sa.inicio)) / sa.total);
    while (true)
    {
        if ((fin & PRIMER_BIT) == (inicio & PRIMER_BIT))
        {
        }
        else if ((inicio & SEGUNDO_BIT) && !(fin & SEGUNDO_BIT))
        {
            codigo ^= SEGUNDO_BIT;
            inicio &= TODOS_MENOS_DOS_PRIMEROS_BITS;
            fin |= SEGUNDO_BIT;
        }
        else
        {
            return;
        }
        inicio <<= 1;
        fin <<= 1;
        fin |= ULTIMO_BIT_UNO;
        codigo <<= 1;
        codigo += bp.leer_bit(entrada);
    }
}

/* Implement ICompresor interface */

void Aritmetico::comprimir(const Simbolo simbolo,
        const FrecuenciasSimbolos &frecuencias,
        const FrecuenciasSimbolos &excluir, std::ostream &salida)
{
    // Transforma el simbolo a comprimir en un modelo propio del compresor (SimboloAritmetico) con un valor para el intervalo
    // propio de este simbolo [inicio, fin) dentro de un contexto que se define a partir de la frecuencia total.
    SimboloAritmetico sa;
    sa.total = 0;

    FrecuenciasSimbolos::const_iterator it = frecuencias.begin();
    while (it != frecuencias.end())
    {
        Simbolo s = (*it).first;

        bool simboloExcluido = excluir.find(s) != excluir.end();

        if (!simboloExcluido)
        {
            uint32_t frecuencia = (*it).second.frecuencia;
//            cout << "frec: " << frecuencia << endl;
            sa.total += frecuencia; // Acumulo la frecuencia total.
            if (s == simbolo) // Si el simbolo fue encontrado dentro de la iteracion:
            {
                // Se setean los valores para el intervalo del simbolo.
                sa.inicio = sa.total - frecuencia;
                sa.fin = sa.total;
            }
        }
        it++;
    }
//    cout << "--------";
    // Una vez transformado al modelo del compresor, se comprime.
    this->comprimir(sa, salida);
    if (simbolo == SimboloEOF)
    {
        // Cuando se detecta el la compresion del EOF, se flushea el stream de compresion.
        this->flush_comprimir(salida);
    }
}

void Aritmetico::comprimirEquiprovable(const Simbolo simbolo,
        const FrecuenciasSimbolos &frecuencias, std::ostream &salida)
{
    // Misma logica que el comprimir, solo que la frecuencia de cada simbolo se asume como 1.
    SimboloAritmetico sa;
    sa.total = 0;

    FrecuenciasSimbolos::const_iterator it = frecuencias.begin();
    while (it != frecuencias.end())
    {
        Simbolo s = (*it).first;

        uint32_t frecuencia = 1;
        sa.total += frecuencia;
        if (s == simbolo)
        {
            sa.inicio = sa.total - frecuencia;
            sa.fin = sa.total;
        }
        it++;
    }
    this->comprimir(sa, salida);
    if (simbolo == SimboloEOF)
    {
        this->flush_comprimir(salida);
    }
}

Simbolo Aritmetico::descomprimir(const FrecuenciasSimbolos &frecuencias,
        const FrecuenciasSimbolos &excluir, std::istream &entrada)
{
    SimboloAritmetico sa;
    Simbolo simbolo;
    // Se calcula previamente la frecuencia total del contexto, necesaria para la descompresion.
    sa.total = calcularFrecuenciaTotal(frecuencias, excluir);
    uint32_t frecuenciaResultado = descomprimir(sa, entrada);

    // Averiguar que simbolo es iterando y llenar el rango del SimboloAritmetico
    uint32_t frecuenciaAcumulada = 0;
    FrecuenciasSimbolos::const_iterator it = frecuencias.begin();
    bool rangoEncontrado = false;
    // Una vez obtenido el SimboloAritmetico del descompresor, busco ese mismo intervalo dentro de la tabla de frecuencias del contexto.
    while (it != frecuencias.end() && !rangoEncontrado)
    {
        simbolo = (*it).first;
        bool simboloExcluido = excluir.find(simbolo) != excluir.end();
        if (!simboloExcluido)
        {
            frecuenciaAcumulada += (*it).second.frecuencia;
            if (frecuenciaAcumulada > frecuenciaResultado)
            {
                sa.fin = frecuenciaAcumulada;
                sa.inicio = frecuenciaAcumulada - (*it).second.frecuencia;
                rangoEncontrado = true;
            }
        }
        it++;
    }
    // Una vez ubicado el simbolo descomprimido, se quita del stream del descompresor.
    quitarCaracterDescomprimido(sa, entrada);
    return simbolo;
}

// Metodo auxiliar para calcular la frecuencia total de un contexto.
uint32_t Aritmetico::calcularFrecuenciaTotal(
        FrecuenciasSimbolos frecuencias, FrecuenciasSimbolos excluir)
{
    uint32_t frecuenciaTotal = 0;
    FrecuenciasSimbolos::iterator it = frecuencias.begin();
    while (it != frecuencias.end())
    {
        Simbolo s = (*it).first;
        bool simboloExcluido = excluir.find(s) != excluir.end();
        if (!simboloExcluido)
        {
            frecuenciaTotal += (*it).second.frecuencia;
        }
        it++;
    }
    return frecuenciaTotal;
}

Simbolo Aritmetico::descomprimirEquiprovable(
        const FrecuenciasSimbolos &frecuencias, std::istream &entrada)
{
    // Misma logica que el descomprimir solo que asumiendo la frecuencia de cada simbolo como 1,
    // en este caso se simplifica el calculo de la frecuencia total siendo el tamaño del contexto.
    SimboloAritmetico sa;
    Simbolo simbolo;
    sa.total = frecuencias.size();
    uint32_t frecuenciaResultado = descomprimir(sa, entrada);

    //averiguar que simbolo es iterando y llenar el rango del SimboloAritmetico
    uint32_t frecuenciaAcumulada = 0;
    FrecuenciasSimbolos::const_iterator it = frecuencias.begin();
    bool rangoEncontrado = false;
    while (it != frecuencias.end() && !rangoEncontrado)
    {
        simbolo = (*it).first;
        frecuenciaAcumulada += 1;
        if (frecuenciaAcumulada > frecuenciaResultado)
        {
            sa.fin = frecuenciaAcumulada;
            sa.inicio = frecuenciaAcumulada - 1;
            rangoEncontrado = true;
        }
        it++;
    }
    quitarCaracterDescomprimido(sa, entrada);
    return simbolo;
}
